package com.kvn.test.util;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

/**
 * <p>
 * Utility class that uses {@code java.lang.reflect} standard library. It
 * provides easy access to the standard reflect methods that are needed usually
 * when dealing with generic object types.
 * </p>
 * 
 * <pre>
 * ReflectionUtil中包含以下几种功能：
	通过Type获取对象class;
	通过Type创建对象;
	获取泛型对象的泛型化参数;
	检查对象是否存在默认构造函数;
	获取指定类型中的特定field类型;
	获取指定类型中的特定method返回类型;
	根据字符串标示获取枚举常量;
 * </pre>
 * 
 * @author Qussay Najjar
 * 
 * @version 1.0
 * @since 2013-09-27
 * 
 * @version 1.1
 * @since 2014-04-13
 * 
 * @link http://qussay.com/2013/09/28/handling-java-generic-types-with-
 *       reflection
 * @link http://www.cnblogs.com/whitewolf/p/4355541.html
 */
public class ReflectionUtil {

	/**
	 * When {@code Type} initialized with a value of an object, its fully
	 * qualified class name will be prefixed with this.
	 * 
	 * @see {@link ReflectionUtil#getClassName(Type)}
	 */
	private static final String TYPE_CLASS_NAME_PREFIX = "class ";
	private static final String TYPE_INTERFACE_NAME_PREFIX = "interface ";

	/*
	 * Utility class with static access methods, no need for constructor.
	 */
	private ReflectionUtil() {
	}

	/**
	 * {@link Type#toString()} value is the fully qualified class name prefixed
	 * with {@link ReflectionUtil#TYPE_NAME_PREFIX}. This method will substring
	 * it, for it to be eligible for {@link Class#forName(String)}.
	 * 
	 * @param type
	 *            the {@code Type} value whose class name is needed.
	 * @return {@code String} class name of the invoked {@code type}.
	 * 
	 * @see {@link ReflectionUtil#getClass()}
	 */
	public static String getClassName(Type type) {
		if (type == null) {
			return "";
		}
		String className = type.toString();
		if (className.startsWith(TYPE_CLASS_NAME_PREFIX)) {
			className = className.substring(TYPE_CLASS_NAME_PREFIX.length());
		} else if (className.startsWith(TYPE_INTERFACE_NAME_PREFIX)) {
			className = className.substring(TYPE_INTERFACE_NAME_PREFIX.length());
		}
		return className;
	}

	/**
	 * Returns the {@code Class} object associated with the given {@link Type}
	 * depending on its fully qualified name.
	 * 
	 * @param type
	 *            the {@code Type} whose {@code Class} is needed.
	 * @return the {@code Class} object for the class with the specified name.
	 * 
	 * @throws ClassNotFoundException
	 *             if the class cannot be located.
	 * 
	 * @see {@link ReflectionUtil#getClassName(Type)}
	 */
	public static Class<?> getClass(Type type) throws ClassNotFoundException {
		String className = getClassName(type);
		if (className == null || className.isEmpty()) {
			return null;
		}
		return Class.forName(className);
	}

	/**
	 * Creates a new instance of the class represented by this {@code Type}
	 * object.
	 * 
	 * @param type
	 *            the {@code Type} object whose its representing {@code Class}
	 *            object will be instantiated.
	 * @return a newly allocated instance of the class represented by the
	 *         invoked {@code Type} object.
	 * 
	 * @throws ClassNotFoundException
	 *             if the class represented by this {@code Type} object cannot
	 *             be located.
	 * @throws InstantiationException
	 *             if this {@code Type} represents an abstract class, an
	 *             interface, an array class, a primitive type, or void; or if
	 *             the class has no nullary constructor; or if the instantiation
	 *             fails for some other reason.
	 * @throws IllegalAccessException
	 *             if the class or its nullary constructor is not accessible.
	 * 
	 * @see {@link Class#newInstance()}
	 */
	public static Object newInstance(Type type) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
		Class<?> clazz = getClass(type);
		if (clazz == null) {
			return null;
		}
		return clazz.newInstance();
	}

	/**
	 * Returns an array of {@code Type} objects representing the actual type
	 * arguments to this object. If the returned value is null, then this object
	 * represents a non-parameterized object.
	 * 
	 * @param object
	 *            the {@code object} whose type arguments are needed.
	 * @return an array of {@code Type} objects representing the actual type
	 *         arguments to this object.
	 * 
	 * @see {@link Class#getGenericSuperclass()}
	 * @see {@link ParameterizedType#getActualTypeArguments()}
	 */
	public static Type[] getParameterizedTypes(Object object) {
		Type superclassType = object.getClass().getGenericSuperclass();
		if (!ParameterizedType.class.isAssignableFrom(superclassType.getClass())) {
			return null;
		}

		return ((ParameterizedType) superclassType).getActualTypeArguments();
	}

	public static Type[] getParameterizedTypes(Class clazz) {
		Type superclassType = clazz.getGenericSuperclass();
		if (!ParameterizedType.class.isAssignableFrom(superclassType.getClass())) {
			return null;
		}

		return ((ParameterizedType) superclassType).getActualTypeArguments();
	}

	/**
	 * Checks whether a {@code Constructor} object with no parameter types is
	 * specified by the invoked {@code Class} object or not.
	 * 
	 * @param clazz
	 *            the {@code Class} object whose constructors are checked.
	 * @return {@code true} if a {@code Constructor} object with no parameter
	 *         types is specified.
	 * @throws SecurityException
	 *             If a security manager, <i>s</i> is present and any of the
	 *             following conditions is met:
	 *             <ul>
	 *             <li>invocation of {@link SecurityManager#checkMemberAccess
	 *             s.checkMemberAccess(this, Member.PUBLIC)} denies access to
	 *             the constructor
	 *
	 *             <li>the caller's class loader is not the same as or an
	 *             ancestor of the class loader for the current class and
	 *             invocation of {@link SecurityManager#checkPackageAccess
	 *             s.checkPackageAccess()} denies access to the package of this
	 *             class
	 *             </ul>
	 * 
	 * @see {@link Class#getConstructor(Class...)}
	 */
	public static boolean hasDefaultConstructor(Class<?> clazz) throws SecurityException {
		Class<?>[] empty = {};
		try {
			clazz.getConstructor(empty);
		} catch (NoSuchMethodException e) {
			return false;
		}
		return true;
	}

	/**
	 * Returns a {@code Class} object that identifies the declared class for the
	 * field represented by the given {@code String name} parameter inside the
	 * invoked {@code Class<?> clazz} parameter.
	 * 
	 * @param clazz
	 *            the {@code Class} object whose declared fields to be checked
	 *            for a certain field.
	 * @param name
	 *            the field name as {@code String} to be compared with
	 *            {@link Field#getName()}
	 * @return the {@code Class} object representing the type of given field
	 *         name.
	 * 
	 * @see {@link Class#getDeclaredFields()}
	 * @see {@link Field#getType()}
	 */
	public static Class<?> getFieldClass(Class<?> clazz, String name) {
		if (clazz == null || name == null || name.isEmpty()) {
			return null;
		}

		Class<?> propertyClass = null;

		for (Field field : clazz.getDeclaredFields()) {
			field.setAccessible(true);
			if (field.getName().equalsIgnoreCase(name)) {
				propertyClass = field.getType();
				break;
			}
		}

		return propertyClass;
	}

	/**
	 * Returns a {@code Class} object that identifies the declared class as a
	 * return type for the method represented by the given {@code String name}
	 * parameter inside the invoked {@code Class<?> clazz} parameter.
	 * 
	 * @param clazz
	 *            the {@code Class} object whose declared methods to be checked
	 *            for the wanted method name.
	 * @param name
	 *            the method name as {@code String} to be compared with
	 *            {@link Method#getName()}
	 * @return the {@code Class} object representing the return type of the
	 *         given method name.
	 * 
	 * @see {@link Class#getDeclaredMethods()}
	 * @see {@link Method#getReturnType()}
	 */
	public static Class<?> getMethodReturnType(Class<?> clazz, String name) {
		if (clazz == null || name == null || name.isEmpty()) {
			return null;
		}

		name = name.toLowerCase();
		Class<?> returnType = null;

		for (Method method : clazz.getDeclaredMethods()) {
			if (method.getName().equals(name)) {
				returnType = method.getReturnType();
				break;
			}
		}

		return returnType;
	}
	
	/**
	 * 返回方法的返回类型中的第一个泛型参数类
	 * @param clazz
	 * @param name
	 * @return
	 */
	public static Class<?> getMethodGenericReturnType(Class<?> clazz, String name) {
		if (clazz == null || name == null || name.isEmpty()) {
			return null;
		}

		name = name.toLowerCase();
		Class<?> returnType = null;

		for (Method method : clazz.getDeclaredMethods()) {
			if (method.getName().equals(name)) {
				ParameterizedType pt= (ParameterizedType) method.getGenericReturnType();
				returnType = (Class) pt.getActualTypeArguments()[0];
				break;
			}
		}

		return returnType;
	}

	/**
	 * Extracts the enum constant of the specified enum class with the specified
	 * name. The name must match exactly an identifier used to declare an enum
	 * constant in the given class.
	 * 
	 * @param clazz
	 *            the {@code Class} object of the enum type from which to return
	 *            a constant.
	 * @param name
	 *            the name of the constant to return.
	 * @return the enum constant of the specified enum type with the specified
	 *         name.
	 * 
	 * @throws IllegalArgumentException
	 *             if the specified enum type has no constant with the specified
	 *             name, or the specified class object does not represent an
	 *             enum type.
	 * 
	 * @see {@link Enum#valueOf(Class, String)}
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static Object getEnumConstant(Class<?> clazz, String name) {
		if (clazz == null || name == null || name.isEmpty()) {
			return null;
		}
		return Enum.valueOf((Class<Enum>) clazz, name);
	}

	public static boolean isPrimitiveOrWrapClass(Class clz) {
		if (clz.isPrimitive()) {
			return true;
		}
		
		return isWrapClass(clz);
	}
	
	public static boolean isPrimitiveOrWrapClass(String className) {
		Class<?> clazz = null;
		try {
			clazz = Class.forName(className.trim());
		} catch (ClassNotFoundException e) {
			throw new RuntimeException("找不到类：" + className);
		}
		
		return isPrimitiveOrWrapClass(clazz);
	}

	public static boolean isWrapClass(Class clz) {
		try {
			return ((Class) clz.getField("TYPE").get(null)).isPrimitive();
		} catch (Exception e) {
			// e.printStackTrace();
			return false;
		}
	}

}